package com.learn.elasticsearch.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.ExistsRequest;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import co.elastic.clients.util.ObjectBuilder;
import com.learn.elasticsearch.domain.EsPage;
import com.learn.elasticsearch.domain.TableDataInfo;
import com.learn.elasticsearch.exception.ServiceException;
import com.learn.elasticsearch.service.EsSearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * @description: java类作用描述
 * @author: MI
 * @email: 448341911@qq.com
 * @createTime: 2024/1/13 0:23
 * @updateUser: MI
 * @updateTime: 2024/1/13 0:23
 * @updateRemark: 修改内容
 * @version: v1.0
 */
@Slf4j
@Service
public class EsearchServiceImpl<T> implements EsSearchService<T> {


    @Resource
    private ElasticsearchClient client;

    @Override
    public boolean hasIndex(String indexName) throws IOException {
        BooleanResponse exists = client.indices().exists(d -> d.index(indexName));
        return exists.value();
    }

    @Override
    public Map<String, IndexState> queryIndex(String indexName) {
        try {
            GetIndexResponse getIndexResponse = client.indices().get(i -> i.index(indexName));
            //stringIndexStateMap.get(indexName).mappings().toString()  //注意序列化问题
            return getIndexResponse.result();
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException("查询索引失败");
        }
    }

    @Override
    public boolean deleteIndex(String indexName) {
        try {
            DeleteIndexResponse response = client.indices().delete(d -> d.index(indexName));
            return response.acknowledged();
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException("删除索引失败");
        }
    }

    @Override
    public boolean createIndex(String indexName) {
        try {
            client.indices().create(c -> c.index(indexName));
        } catch (IOException e) {
            log.error("索引创建失败：{}", e.getMessage());
            throw new ServiceException("创建索引失败");
        }
        return true;
    }


    /**
     * 创建索引，不允许外部直接调用
     *
     * @param indexName
     * @param mapping
     * @throws IOException
     */
    private boolean createIndex(String indexName, Map<String, Property> mapping) throws IOException {
        CreateIndexResponse createIndexResponse = client.indices().create(c -> {
            c.index(indexName).mappings(mappings -> mappings.properties(mapping));
            return c;
        });
        return createIndexResponse.acknowledged();
    }

    @Override
    public void reCreateIndex(String indexName, Map<String, Property> mapping) {
        try {
            if (this.hasIndex(indexName)) {
                this.deleteIndex(indexName);
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException("删除索引失败");
        }

        try {
            this.createIndex(indexName, mapping);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException("重新创建索引失败");
        }
    }

    @Override
    public String insertDocument(String indexName, Map<String, Object> obj, String id) {
        try {
            IndexRequest.Builder builder = new IndexRequest.Builder().index(indexName).document(obj);
            if (StringUtils.isNotBlank(id)) {
                builder.id(id);
            }
            IndexResponse index = client.index(builder.build());
            return index.id();
        } catch (IOException e) {
            log.error("数据插入ES异常：{}", e.getMessage());
            throw new ServiceException("ES新增数据失败");
        }
    }

    @Override
    public boolean updateDocument(String indexName, Map<String, Object> obj, String id) {
        try {
            client.update(u -> u.index(indexName).id(id).doc(obj), String.class);
            return true;
        } catch (IOException e) {
            log.error("数据修改ES异常：{}", e.getMessage());
            throw new ServiceException("ES修改数据失败");
        }
    }

    @Override
    public Map searchDocument(String indexName, String id) {
        try {
            GetResponse<Map> getResponse = client.get(g -> g.index(indexName).id(id), Map.class);
            return getResponse.source();
        } catch (IOException e) {
            log.error("查询ES异常：{}", e.getMessage());
            throw new ServiceException("查询ES数据失败");
        }
    }

    @Override
    public boolean deleteDocument(String indexName, String id) {
        try {
            client.delete(d -> d.index(indexName).id(id));
        } catch (IOException e) {
            log.error("删除Es数据异常：{}", e.getMessage());
            throw new ServiceException("数据删除失败");
        }
        return true;
    }

    @Override
    public boolean batchInsertDocument(String indexName, List<Map<String, Object>> objs) {
        try {
            List<BulkOperation> bulkOperationArrayList = new ArrayList<>();
            for (Map<String, Object> obj : objs) {
                bulkOperationArrayList.add(BulkOperation.of(o -> o.index(i -> i.document(obj))));
            }
            client.bulk(b -> b.index(indexName).operations(bulkOperationArrayList));
            return true;
        } catch (IOException e) {
            log.error("数据插入ES异常：{}", e.getMessage());
            throw new ServiceException("ES新增数据失败");
        }
    }

    /**
     * 非主键查询
     *
     * @param indexName
     * @return
     */
    private TableDataInfo searchDocumentList(String indexName, Query query, EsPage esPage) {
        try {
            SearchRequest.Builder request = new SearchRequest.Builder().index(indexName);
            if (query != null) {
                request.query(query);
            }
            request.from(esPage.getPageNum() - 1);
            request.size(esPage.getPageSize());
            if (esPage.getOrderBy() != null) {
                request.sort(f -> f.field(o -> o.field(esPage.getOrderBy()).order("asc".equals(esPage.getType()) ? SortOrder.Asc : SortOrder.Desc)));
            }
            List<String> includes = esPage.getIncludes();
            if (includes != null && includes.size() > 0) {
                request.source(a -> a.filter(v -> v.includes(includes)));
            }
            SearchResponse<Map> search = client.search(request.build(), Map.class);
            List<Map<String, Object>> data = new ArrayList<>();
            HitsMetadata<Map> hits = search.hits();
            TotalHits total = hits.total();
            for (Hit<Map> hit : hits.hits()) {
                data.add(hit.source());
            }
            return new TableDataInfo(data, (int) total.value());
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException(String.format("【查询 -> 失败】从es中查询分析后的日志出错，错误信息为：{}", e.getMessage()));
        }
    }

    @Override
    public TableDataInfo searchTermDocumentList(String indexName, EsPage esPage) {
        Query query = null;
        String cond = esPage.getCond();
        if (cond != null) {
            List<Query> list = new ArrayList<>();
            String[] split = cond.split(",");
            for (int i = 0; i < split.length; i++) {
                String[] split1 = split[i].split(":");
                list.add(QueryBuilders.match().field(split1[0]).query(split1[1])
                        //.value(split1[1])
                        .build()._toQuery());
            }
            query = QueryBuilders.bool().must(list).build()._toQuery();
        }
        return searchDocumentList(indexName, query, esPage);
    }

    public boolean existsIndex(String index) {
        try {
            ExistsRequest existsRequest = new ExistsRequest.Builder().index(index).build();
            BooleanResponse response = client.indices().exists((Function<co.elastic.clients.elasticsearch.indices.ExistsRequest.Builder, ObjectBuilder<co.elastic.clients.elasticsearch.indices.ExistsRequest>>) existsRequest);
            return response.value();
        } catch (IOException e) {
            log.error("There is an error while getting index", e);
        }
        return false;
    }

    @Override
    public boolean createIndex(String indexName, String aliasesName, int numOfShards, Map<String, Property> properties) {
        try {
            TypeMapping typeMapping = new TypeMapping.Builder().properties(properties).build();
            IndexSettings indexSettings = new IndexSettings.Builder().numberOfShards(String.valueOf(numOfShards)).build();
            CreateIndexRequest createIndexRequest = new CreateIndexRequest.Builder().index(indexName).aliases(aliasesName, new Alias.Builder().isWriteIndex(true).build()).mappings(typeMapping).settings(indexSettings).build();
            CreateIndexResponse response = client.indices().create(createIndexRequest);
            return response.acknowledged();
        } catch (IOException e) {
            log.error("There is an error while creating index", e);
        }
        return false;
    }

    @Override
    public boolean deleteIndex(List<String> indexList) {
        try {
            DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest.Builder().index(indexList).build();
            DeleteIndexResponse response = client.indices().delete(deleteIndexRequest);
            return response.acknowledged();
        } catch (IOException e) {
            log.error("There is an error while deleting index", e);
        }
        return false;
    }

    @Override
    public boolean existsDocument(String index, String id, Class clazz) {
        try {
            GetRequest getRequest = new GetRequest.Builder().index(index).id(id).build();
            GetResponse<T> getResponse = client.get(getRequest, clazz);
            return getResponse.found();
        } catch (IOException e) {
            log.error("There is an error while judging if the document exists", e);
        }
        return false;
    }

    @Override
    public IndexResponse saveOrUpdateDocument(String index, String id, Object t) {
        try {
            IndexRequest<T> indexRequest = new IndexRequest.Builder<T>().index(index).id(id).document((T) t).build();
            return client.index(indexRequest);
        } catch (IOException e) {
            log.error("There is an error while saving the document", e);
        }
        return null;
    }

    @Override
    public IndexResponse saveOrUpdateDocument(String index, Object t) {
        try {
            IndexRequest<T> indexRequest = new IndexRequest.Builder<T>().index(index).document((T) t).build();
            return client.index(indexRequest);
        } catch (IOException e) {
            log.error("There is an error while saving the document", e);
        }
        return null;
    }

    @Override
    public T getById(String index, String id, Class clazz) {
        try {
            GetRequest getRequest = new GetRequest.Builder().index(index).id(id).build();
            GetResponse<T> getResponse = client.get(getRequest, clazz);
            return getResponse.source();
        } catch (IOException e) {
            log.error("There is an error while getting the document", e);
        }
        return null;
    }

    @Override
    public List<T> getByIdList(String index, List<String> idList, Class clazz){
        try {
            List<T> tList = new ArrayList<>(idList.size());
            for (String id : idList) {
                tList.add((T) client.get(new GetRequest.Builder().index(index).id(id).build(), clazz).source());
            }
            return tList;
        } catch (IOException e) {
            log.error("There is an error while getting the document list", e);
        }
        return null;
    }

    @Override
    public HitsMetadata<T> searchByPages(String index, Integer pageNo, Integer pageSize, Class clazz) {
        try {
            SearchRequest searchRequest = new SearchRequest.Builder().index(Collections.singletonList(index)).from(pageNo).size(pageSize).build();
            SearchResponse<T> searchResponse = client.search(searchRequest, clazz);
            return searchResponse.hits();
        } catch (IOException e) {
            log.error("There is an error while searching by pages", e);
        }
        return null;
    }

    public boolean deleteById(String index, String id) {
        try {
            DeleteRequest deleteRequest = new DeleteRequest.Builder().index(index).id(id).build();
            DeleteResponse deleteResponse = client.delete(deleteRequest);
            return "deleted".equals(deleteResponse.result().jsonValue());
        } catch (IOException e) {
            log.error("There is an error while deleting id document", e);
        }
        return false;
    }
}
